--[[
Wordfilter.lua
Allows applying lua-pattern based word filters on the fly.
These filters convert a string matching a given pattern into some other word(s).

By Tyrone.
Last updated 19/02/08
-------------------------
Changelog:
0.1
Stores the patterns in an SQL database.
]]

--Constants--
_,bot_name = VH:GetConfig("config", "hub_security")
-------------

--Global Variables and Tables--
threshold = 0;
patterns = {} --each of these tables is in the form tablename[id] = pattern/replace/strength/active
replaces = {} --I could use three dimensional tables ... but I don't wanna
strengths = {}
actives = {}
deleteds = {}
-----------

function Main()
	SendQuery("CREATE TABLE IF NOT EXISTS wordfilter (id INT(11) NOT NULL AUTO_INCREMENT, pattern TEXT NOT NULL, `replace` TEXT NOT NULL, strength TINYINT(2) NOT NULL, active TINYINT(1) NOT NULL, deleted TINYINT(1), PRIMARY KEY (id));")
	SendMessageToHub("Hub-wide word filters are now active at a strength threshold of zero.")
	loadFromSQL()
end

function UnLoad()
	SendMessageToHub("Hub-wide word filters are now inactive.")
end

function SendQuery(query)
	local res, err = VH:SQLQuery(query)
	if (not res) then
		SendMessageToHub("An error has occurred!  :( \n"..err.."\nQuery="..query, bot_name)
	end
	print(query)
end

function TagsToText(s)
	s = string.gsub(s, "&quot;", '"')
	s = string.gsub(s, "&gt;", ">")
	s = string.gsub(s, "&lt;", "<")
	s = string.gsub(s, "&#92;", "\\")
	s = string.gsub(s, "&#47;", "/")
	s = string.gsub(s, "&#36;", "$")
	return s
end

function TextToTags(s)
	s = string.gsub(s, "'", "''")
	s = string.gsub(s, '"', "&quot;")
	s = string.gsub(s, ">", "&gt;")
	s = string.gsub(s, "<", "&lt;")
	s = string.gsub(s, "\\", "&#92;")
	s = string.gsub(s, "/", "&#47;")
	s = string.gsub(s, "&#36;", "$")
	return s
end

function loadFromSQL()
	ret, rows = VH:SQLQuery("SELECT id, pattern, `replace`, strength, active, deleted FROM wordfilter;")
	for i = 0, rows, 1 do
		res, id, pattern, replace, strength, active, deleted = VH:SQLFetch(i)
		if (not res) then print("not res"); break; end
		id = tonumber(id)
		if (pattern == nil) then break end
		if (replace == nil) then break end
		if (strength == nil) then break end
		if (active == nil) then break end
		patterns[id] = TagsToText(pattern)
		replaces[id] = TagsToText(replace)
		strengths[id] = tonumber(strength)
		actives[id] = tonumber(active)
		deleteds[id] = tonumber(deleted)
	end
	
end

function nextid()
	SendQuery("SELECT id FROM wordfilter ORDER BY id DESC;")
	local res, id = VH:SQLFetch(0)
	if (tonumber(id) == nil) then
		--print("I got nil from VH:SQLFetch")
		return 1
	else
		return tonumber(id) + 1
	end
end

--The syntax for adding a new filter is as such:
--.wf add {pattern} {replacement} strengthint
--They are always deactivated by default.
--They can get automatically activated if below threshold.
function VH_OnParsedMsgChat(nick, data)	
	--This section deals with command parsing.
	if (string.find(data, "^%.wf")) then
		--They are issuing a command.  Only ops can do this.  Valid commands are:
		--.wf add {pattern} {replacement} strengthint
		--.wf show
		--.wf activate id
		--.wf deactivate id
		--.wf [help|?]
		--.wf delete id
		--.wf threshold level
		if (UserClass(nick) < 5) then
			SendMessageToUser("Only operators can manipulate word filters.", nick)
			return 0
		end
		
		--Let's remove the .wf
		data = string.sub(data, 5)
		if (data == nil) then
			showHelp(nick)
			return 0
		end
		
		--print("Data after removing .wf:"..data)
		local _,_,command = string.find(data, "(%a+)%s")
		
		if (command == nil) then
			command = data
		end
		--print("Extracted command:"..command)
		data = string.sub(data, string.len(command)+2) --remove command from there too
		--print("Data after removing command:"..data)
		
		if (command == "show") then
			showFilters(nick)
		elseif (command == "activate") then
			if (data == nil) then
				--There's nothing proceeding the command
				showHelp(nick)
				return 0
			end
			
			data = tonumber(data)
			if (patterns[data] == nil) then
				SendMessageToUser("A filter with ID: "..data..", does not exist.  Use .wf show to find valid IDs.", nick)
			else
				actives[data] = 1
				SendMessageToUser("The filter "..patterns[data].." -> "..replaces[data].." has been forcibly activated.", nick)
				SendQuery("UPDATE wordfilter SET active=1 WHERE id = "..data..";")
			end
			return 0
		elseif (command == "deactivate") then
			if (data == nil) then
				--There's nothing proceeding the command
				showHelp(nick)
				return 0
			end
			
			data = tonumber(data)
			if (patterns[data] == nil) then
				SendMessageToUser("A filter with ID: "..data..", does not exist.  Use .wf show to find valid IDs.", nick)
			else
				actives[data] = 0
				SendMessageToUser("The filter "..patterns[data].." -> "..replaces[data].." has been forcibly deactivated.", nick)
				SendQuery("UPDATE wordfilter SET active=0 WHERE id = "..data..";")
			end
			return 0
		elseif (command == "delete") then
			if (data == nil) then
				--There's nothing proceeding the command
				showHelp(nick)
				return 0
			end
			
			data = tonumber(data)
			if (data == nil) then showHelp(nick); return 0; end
			if (patterns[data] == nil) then
				SendMessageToUser("A filter with ID: "..data..", does not exist.  Use .wf show to find valid IDs.", nick)
			else
				SendMessageToUser("The filter "..patterns[data].." -> "..replaces[data].." has been deleted.", nick)
				deleteds[data] = 1
				SendQuery("UPDATE wordfilter SET deleted=1 WHERE id = "..data..";")
			end
			return 0
		elseif (command == "threshold") then
			if (data == nil) then
				--There's nothing proceeding the command
				showHelp(nick)
				return 0
			end
			
			data = tonumber(data)
			if (data == nil) then showHelp(nick); return 0; end
			
			if (data < 0) or (data > 9) then
				SendMessageToUser("Threshold boundaries are 0 to 9.", nick)
			else
				SendMessageToUser("Threshold has been set to "..data.." from "..threshold..".", nick)
				threshold = data
				SendMessageToHub("Hub-wide word filters are now active at a strength threshold of "..threshold..".")
			end
			return 0
		elseif (command == "add") then
			if (data == nil) then
				--There's nothing proceeding the command
				showHelp(nick)
				return 0
			end
			--Ok, we're going to add a wordfilter.  First check their syntax.  First character should be {
			if (string.sub(data,1,1) == "{") then --syntax looks good
				first = string.match(data, "(%b{})")
				if (first == nil) then
					showHelp(nick)
					return 0
				end
				--print("First="..first)
				--Remove this match from data.
				data = string.sub(data,string.len(first)+2)
				if (data == nil) then
					showHelp(nick)
					return 0
				end
				
				second = string.match(data, "(%b{})")
				if (second == nil) then
					showHelp(nick)
					return 0
				end
				--print("Second="..second)
				--Remove this match from data.
				data = string.sub(data,string.len(second)+2)
				
				str = tonumber(data)
				
				if (str == nil) then
					showHelp(nick)
					return 0
				end
				
				if (str > 9) or (str < 0) then
					SendMessageToUser("Strength's max is 9 and min is 0.  The strength has been set to 9.")
					str = 9
				end
				--print("str="..str)
				--Remove those brackets
				first = string.sub(first,2,string.len(first)-1)
				second = string.sub(second,2,string.len(second)-1)			
				SendQuery("INSERT INTO wordfilter (id, pattern, `replace`, strength, active, deleted) VALUES (NULL,'"..TextToTags(first).."','"..TextToTags(second).."','"..str.."','0','0');")
				ni = nextid() - 1
				--print(ni);
				patterns[ni] = first
				replaces[ni] = second
				strengths[ni] = str
				deleteds[ni] = 0
				if (str <= threshold) then
					actives[nextid] = 1
				else
					actives[nextid] = 0
				end
				SendMessageToUser("You've set up a wordfilter: "..first.." -> "..second.." with a strength rating of "..str..".", nick)

			else
				showHelp(nick)
				return 0
			end
		else
			showHelp(nick)
		end
		return 0
	end
	--Don't wordfilter commands.
	if (string.find(data, "^%.")) then
		return 1
	end
	
	--This is to prevent this script from discarding the hub's built in
	--message sending unless it really has to.  If you send the message with
	--SendMessageToHub, it lags.  So only do that if necessary.
	local found = false
	for i=1, #patterns, 1 do
		if (found == false) then
			if ((strengths[i] <= threshold) or (actives[i] == 1)) and (deleteds[i] == 0) then
				if (string.find(data, patterns[i])) then
					found = true
					break
				end
			end
		end
	end
	
	if (found == false) then return 1 end
	--This section deals with the actual word filtering.  For safety reasons, it happens
	--after the command parsing.
	for i=1, #patterns, 1 do
		if ((strengths[i] <= threshold) or (actives[i] == 1)) and (deleteds[i] == 0) then
			data = string.gsub(data, patterns[i], replaces[i])
		end
	end
	SendMessageToHub(data, nick)
	return 0
end

function showFilters(nick)
	--Should be in this form:
	--Current filters:
	--ID: 0, strength: 0, sup -> RON PAUL
	--[[
	if (table.maxn(patterns) == 0) then
		SendMessageToUser("No filters exist in database.", nick)
		return nil
	end
	--]]
	--print(patterns[2])
	--print(deleteds[2])
	SendMessageToUser("Current filters:", nick)
	for i=1, #patterns, 1 do
		local forceactive = "no"
		if (deleteds[i] == 0) then
			if (actives[i] == 1) then forceactive = "yes" end
			SendMessageToUser("ID: "..i..", strength: "..strengths[i]..", force active: "..forceactive..",   ::   "..patterns[i].." -> "..replaces[i],nick)
		end
	end
end

function showHelp(nick)
	if (UserClass(nick) < 5) then
		SendMessageToUser("Only operators can manipulate word filters.", nick)
		return nil
	end
	SendMessageToUser("Usage:", nick)
	DisplayHelpMessage("wf", "?","Displays this help message.", nick)
	DisplayHelpMessage("wf", "add {pattern} {replacement} <strength>",
					   "Creates a new word filter from pattern to replacement.  Note that the {} brackets "..
					   "should be included.  Strength determines where the threshold should be in order for "..
					   "the wordfilter to automatically activate.  It is a number between 0 and 9.", nick)
	DisplayHelpMessage("wf", "activate <id>","Forces a word filter to activate.", nick)
	DisplayHelpMessage("wf", "deactivate <id>","Forces a word filter to deactivate.", nick)
	DisplayHelpMessage("wf", "show","Shows all word filters in the database and their IDs.", nick)
	DisplayHelpMessage("wf", "threshold <level>","Sets the global strength threshold for automatically enabling filters.  Number between 0-9.", nick)
	DisplayHelpMessage("wf", "delete <id>","Removes a word filter from the database.", nick)
end



--##########################################--
--Everything Below This Line is COMMON Functions--
--##########################################--

--SendMessageToHub
--Sends a message to the entire hub from a user.  If "from" parameter is
--neglected, the message will be sent from hub_security.
function SendMessageToHub(data, from)
	if (from == nil) then
		--Send it from hub security
		from = bot_name
	end
	return VH:SendDataToAll("<"..from.."> "..data.."|",0,10)
end

--SendMessageToUser
--Sends a message to a specific user in mainchat.  If "nick" parameter is
--neglected, the message will be sent to the whole hub.  If the "from" parameter
--is neglected, the message will be sent from hub_security.
function SendMessageToUser(data, nick, from)
	if (from == nil) then
		--Send it from hub security.
		from = bot_name
	end
	
	if (nick == nil) then
		--Send it to the whole hub.
		return VH:SendDataToAll("<"..from.."> "..data.."|",0,10)
	end
	
	return VH:SendDataToUser("<"..from.."> "..data.."|", nick)
end

function SendPrivateMessageToUser(user, data, from)
    return VH:SendDataToUser("$To: "..user.." From: "..from.." $<"..from.."> "..data.."|", user);
end

function DisplayHelpMessage(cmdname, cmd, msg, nick)
    SendMessageToUser("        ."..cmdname.." "..cmd, nick, bot_name)
    SendMessageToUser("        "..msg, nick, bot_name)
end

function UserClass(nick)
	res, class = VH:GetUserClass(nick)
	if res and class then return class else return 0 end
end

function UserIsOnline(nick)
	result, sIP = VH:GetUserIP(nick)
	if (string.len(sIP) < 7) then
		return false
	else
		return true
	end
end

function UserIP(nick)
	result, sIP = VH:GetUserIP(nick)
	if (string.len(sIP) < 7) then
		return 0
	else
		return sIP
	end
end

function SendEmoteToHub(data, from)
	result, err = VH:SendDataToAll("<"..from.."> ".."/me "..data.."|",0,10)
	return 1
end

function KickAllUsersWithIP(ip, reason)
	_,nicklist = VH:GetNickList()
	nicklist = string.sub(nicklist, 11)
	local flag = false
	for names in string.gmatch(nicklist, "([^%$]+)%$%$") do
		if (ip == UserIP(names)) then
			if (flag == false) then
				VH:KickUser(security, names, reason)
				flag = true
			else
				VH:CloseConnection(names)
			end
		end
	end
end

function getNumberOfWords(s)
	local wc = 0
	for _ in string.gmatch(s, "%a+") do
		wc = wc + 1
	end
	return wc
end

function removeAllSymbols(s)
	local outstring = ""
	for l in string.gmatch(s, "%w") do
		outstring = outstring + l
	end
	return outstring
end

function pluralise(s, i)
	if (i ~= 1) then
		if (string.sub(s, string.len(s)) == "s") then
			return s.."es"
		else
			return s.."s"
		end
	else
		return s
	end
end
